use crate::config::app_error::AppResult;
use crate::extension::index::index::query::index_attribute::{
    IndexAttribute, IndexAttributeFactory, IndexAttributeType,
};
use anyhow::anyhow;
use halo_model::{ExtensionGVK, ExtensionOperator, GVK};
use serde::{Deserialize, Serialize};
use std::any::Any;
use std::collections::HashSet;
use std::hash::{Hash, Hasher};

#[derive(Debug, Clone)]
pub struct IndexSpec<T: ExtensionGVK> {
    pub name: String,
    pub order: OrderType,
    pub unique: bool,
    pub index_func: IndexAttributeType<T>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default, Hash, PartialEq)]
pub enum OrderType {
    #[default]
    ASC,
    DESC,
}

impl<T: ExtensionGVK> IndexSpec<T> {
    pub fn new(
        name: String,
        order: OrderType,
        unique: bool,
        index_func: IndexAttributeType<T>,
    ) -> Self {
        IndexSpec {
            name,
            index_func,
            order,
            unique,
        }
    }

    pub fn new_type(name: &str, index_func: IndexAttributeType<T>) -> Self {
        IndexSpec {
            name: name.to_string(),
            index_func,
            order: OrderType::ASC,
            unique: false,
        }
    }
}

pub const PRIMARY_INDEX_NAME: &str = "metadata.name";
pub const LABEL_PATH: &str = "metadata.labels";
impl<T: ExtensionGVK> Hash for IndexSpec<T> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.name.hash(state);
    }
}

impl<T: ExtensionGVK> PartialEq for IndexSpec<T> {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
    }
}
impl<T: ExtensionGVK> Eq for IndexSpec<T> {}

pub fn primary_key_index_spec<T: ExtensionGVK>() -> IndexSpec<T> {
    IndexSpec::new(
        "metadata.name".to_string(),
        OrderType::ASC,
        true,
        IndexAttributeFactory::simple_attribute("".to_string(), |e: &T| {
            e.get_metadata().get_name().to_string()
        }),
    )
}

pub fn label_index_spec<T: ExtensionGVK>() -> IndexSpec<T> {
    IndexSpec::new(
        "metadata.labels".to_string(),
        OrderType::ASC,
        false,
        IndexAttributeFactory::multi_value_attribute("".to_string(), |e: &T| {
            label_index_value_func(e)
        }),
    )
}

pub fn get_object_primary_key<T: ExtensionGVK>(t: &T) -> String {
    t.get_metadata().get_name().to_string()
}

fn label_index_value_func<T: ExtensionGVK>(e: &T) -> HashSet<String> {
    e.get_metadata()
        .get_labels()
        .map(|t| t.into_iter().map(|(k, v)| format!("{k}={v}")).collect())
        .unwrap_or(HashSet::new())
}

pub fn label_key_value_pair(indexed_label_key: &str) -> AppResult<(String, String)> {
    if let Some(idx) = indexed_label_key.find("=") {
        let key = &indexed_label_key[0..idx];
        let value = &indexed_label_key[idx + 1..];
        Ok((key.to_string(), value.to_string()))
    } else {
        Err(anyhow!("Invalid label key-value pair: {}", indexed_label_key).into())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::extension::index::index::query::index_attribute::IndexAttributeFactory;
    use crate::extension::index::index::query::index_spec::{IndexSpec, OrderType};
    use crate::get_scheme_manager;
    use crate::tests::index_view_data_set::FakeExtension;
    use halo_model::Metadata;
    use std::collections::HashMap;

    #[test]
    fn equals_verifier() {
        let spec1 = IndexSpec::new(
            "metadata.name".to_string(),
            OrderType::ASC,
            true,
            IndexAttributeFactory::simple_attribute(
                "FakeExtension".to_string(),
                |e: &FakeExtension| e.get_metadata().get_name().to_string(),
            ),
        );
        let spec2 = IndexSpec::new(
            "metadata.name".to_string(),
            OrderType::ASC,
            true,
            IndexAttributeFactory::simple_attribute(
                "FakeExtension".to_string(),
                |e: &FakeExtension| e.get_metadata().get_name().to_string(),
            ),
        );
        assert_eq!(spec1, spec2);

        assert!(spec1.eq(&spec2));

        let spec3 = IndexSpec::new(
            "metadata.name".to_string(),
            OrderType::DESC,
            false,
            IndexAttributeFactory::simple_attribute(
                "FakeExtension".to_string(),
                |e: &FakeExtension| e.get_metadata().get_name().to_string(),
            ),
        );

        assert_eq!(spec1, spec3);

        assert!(spec1.eq(&spec3));

        let spec4 = IndexSpec::new(
            "slug".to_string(),
            OrderType::ASC,
            true,
            IndexAttributeFactory::simple_attribute(
                "FakeExtension".to_string(),
                |e: &FakeExtension| e.get_metadata().get_name().to_string(),
            ),
        );
        assert!(!spec1.eq(&spec4));
    }

    #[test]
    fn test_label_key_value_pair() {
        let pair = label_key_value_pair("key=value");
        assert!(pair.is_ok());
        let (k, v) = pair.unwrap();
        assert_eq!(k, "key");
        assert_eq!(v, "value");

        let pair = label_key_value_pair("key=value=1");
        assert!(pair.is_ok());
        let (k, v) = pair.unwrap();
        assert_eq!(k, "key");
        assert_eq!(v, "value=1");

        let pair = label_key_value_pair("key");
        assert!(pair.is_err());
        if let Err(e) = pair {
            assert_eq!(e.to_string(), "error:`Invalid label key-value pair: key`");
        }
    }

    #[tokio::test]
    async fn test_label_index_value_func() {
        let mut extension = FakeExtension::new();

        extension.set_metadata(Metadata::default());
        let mut scheme_manager = get_scheme_manager!().unwrap();
        scheme_manager.register::<FakeExtension>().await;

        let spec = label_index_value_func::<FakeExtension>(&extension);

        assert!(spec.is_empty());

        let mut meta = Metadata::default();
        let mut map = HashMap::new();
        map.insert("key".to_string(), "value".to_string());
        map.insert("key1".to_string(), "value1".to_string());

        meta.set_labels(map);
        extension.set_metadata(meta);

        let spec = label_index_value_func::<FakeExtension>(&extension);
        assert_eq!(spec.len(), 2);
        assert!(spec.contains("key=value"));
        assert!(spec.contains("key1=value1"));
    }
}
